#pragma once

#include "Modifies.h"
#include "Utilities.h"

Modifies::Modifies(void){
	modifiesLVarTable = LVarTableType();
	modifiesLineTable =  LineTableType();
	modifiesPVarTable =  PVarTableType();
	modifiesProcTable =  ProcTableType();
	modifiesLineBitMap =  Type::Map::Bit();
	modifiesProcBitMap =  Type::Map::Bit();
	lastModifiedLVMatrix = Type::Map::LineMatrix();
	nextModifiedLVMatrix = Type::Map::LineMatrix();
}
Modifies::~Modifies(void){
	clear();
}

void Modifies::clear(){
	modifiesLVarTable.clear();
	modifiesLineTable.clear();
	modifiesPVarTable.clear();
	modifiesProcTable.clear();
	modifiesLineBitMap.clear();
	modifiesProcBitMap.clear();
	lastModifiedLVMatrix.clear();
	nextModifiedLVMatrix.clear();
}

// set modifies relationship for the given program line and variable, returns status of operation
bool Modifies::setLineModifies(Type::Index::Line line, Type::Entity::Line entityType, Type::Index::Var var){
	// invalid indices or entity
	if (!Utilities::isValidLineIndex(line) || !Utilities::isValidVarIndex(var) 
		|| !Utilities::isValidLineEntity(entityType)){
			return false;
	}

	Type::List::Var varList;
	Type::Map::EntityLine entityLineMap;
	Type::List::Line allLineList;
	Type::List::Line lineList;
	bool isNonExisted = true;

	/********************** modifiesVarTable*******************************/

	// load existing var list of the line 
	if(isLineExistsInVar(line)){
		varList = modifiesLVarTable.at(line);
		modifiesLVarTable.erase(line);
	}

	if(!Utilities::isExistInList(varList,var)){
		// append new var list
		varList.push_back(var);
	}else{
		isNonExisted = false;
	}
	modifiesLVarTable.insert(std::make_pair<Type::Index::Line, Type::List::Var>(line, varList));

	/********************** modifiesLineTable*******************************/

	// load existing line list of the var 
	if(isVarExistsInLine(var)){
		entityLineMap = modifiesLineTable.at(var);
		modifiesLineTable.erase(var);

		// find ALL entity in EntityLineMap
		if(!entityLineMap.empty() && entityLineMap.find(allLineType) != entityLineMap.end()){
			allLineList = entityLineMap.at(allLineType);
			entityLineMap.erase(allLineType);
		}

		// find inserted entity in EntityLineMap
		if(!entityLineMap.empty() && entityLineMap.find(entityType) != entityLineMap.end()){
			lineList = entityLineMap.at(entityType);
			entityLineMap.erase(entityType);
		}
	}

	if(!Utilities::isExistInList(allLineList, line)){
		// append new line list
		allLineList.push_back(line);
		lineList.push_back(line);
	}else{
		isNonExisted = false;
	}
	entityLineMap.insert(std::make_pair<Type::Entity::Line, Type::List::Line>(allLineType, allLineList));	
	entityLineMap.insert(std::make_pair<Type::Entity::Line, Type::List::Line>(entityType, lineList));
	modifiesLineTable.insert(std::make_pair<Type::Constant, Type::Map::EntityLine>(var, entityLineMap));

	return isNonExisted;
}

bool Modifies::setProcModifies(Type::Index::Proc proc, Type::Index::Var var){
	// invalid indexes
	if (!Utilities::isValidProcIndex(proc) || !Utilities::isValidVarIndex(var)){
		return false;
	}

	Type::List::Var varList;
	Type::List::Proc procList;
	bool isNonExisted = true;

	/********************** usesPVarTable*******************************/

	// load existing var list of the proc
	if(isProcExistsInVar(proc)){
		varList = modifiesPVarTable.at(proc);
		modifiesPVarTable.erase(proc);
	}

	if(!Utilities::isExistInList(varList,var)){
		// append new variable
		varList.push_back(var);
	}else{
		isNonExisted = false;
	}
	modifiesPVarTable.insert(std::make_pair<Type::Index::Proc, Type::List::Var>(proc, varList));

	/********************** usesProcTable*******************************/

	// load existing proc list of the var
	if(isVarExistsInProc(var)){
		procList = modifiesProcTable.at(var);
		modifiesProcTable.erase(var);
	}

	if(!Utilities::isExistInList(procList, proc)){
		// append new proc
		procList.push_back(proc);
	}else{
		isNonExisted = false;
	}
	modifiesProcTable.insert(std::make_pair<Type::Index::Var, Type::List::Proc>(var, procList));

	return isNonExisted;
}

// set the bitmap (square matrix) for line modifies relationship, 
// returns status of operation
bool Modifies::setLineModifiesBitMap(Type::Map::Bit& bitMap){
	if(!Utilities::isValidBitMap(bitMap)){
		return false;
	}
	modifiesLineBitMap = bitMap;

	return true;
}

// set the bitmap (square matrix) for proc modifies relationship, 
// returns status of operation
bool Modifies::setProcModifiesBitMap(Type::Map::Bit& bitMap){
	if(!Utilities::isValidBitMap(bitMap)){
		return false;
	}

	modifiesProcBitMap = bitMap;

	return true;
}

// set the last modified line-variable matirx
bool Modifies::setLastModified(Type::Map::LineMatrix& lineVariableMatrix){
	if(!Utilities::isValidLineMatrix(lineVariableMatrix)){
		return false;
	}

	lastModifiedLVMatrix = lineVariableMatrix;

	return true;
}

// set the next modified line-variable matrix
bool Modifies::setNextModified(Type::Map::LineMatrix& lineVariableMatrix){
	if(!Utilities::isValidLineMatrix(lineVariableMatrix)){
		return false;
	}

	nextModifiedLVMatrix = lineVariableMatrix;

	return true;
}

// get all the modified variables
const Type::List::Var& Modifies::getModifiedVars(){
	Type::List::Var* varList = new Type::List::Var();

	for(LineTableType::iterator it=modifiesLineTable.begin(); it!=modifiesLineTable.end(); it++){
		varList->push_back(it->first);
	}

	return *varList;
}

// get the variable modified by the given line
const Type::List::Var& Modifies::getModifiedVarsByLine(Type::Index::Line line){
	if(Utilities::isValidLineIndex(line) && isLineExistsInVar(line)){
		return modifiesLVarTable.at(line);
	}

	return *(new Type::List::Var());
}

// get the variable modified by the given proc
const Type::List::Var& Modifies::getModifiedVarsByProc(Type::Index::Proc proc){
	if(Utilities::isValidProcIndex(proc) && isProcExistsInVar(proc)){
		return modifiesPVarTable.at(proc);
	}

	return *(new Type::List::Var());
}

// get the list of lines that modifies the given variable
const Type::List::Line& Modifies::getLinesModify(Type::Index::Var var){
	if(Utilities::isValidVarIndex(var) && isVarExistsInLine(var)){
		return modifiesLineTable.at(var).at(allLineType);
	}

	return *(new Type::List::Line());
}

// get the list of lines with given entity that modifies the given variable
const Type::List::Line& Modifies::getLinesModify(Type::Index::Var var, Type::Entity::Line entity){
	
	if(Utilities::isValidVarIndex(var) && isVarExistsInLine(var)
		&& Utilities::isValidLineEntity(entity)){
		Type::Map::EntityLine* entityLineMap = &modifiesLineTable.at(var);
		
		if(entityLineMap->find(entity) != entityLineMap->end()){
			return entityLineMap->at(entity);
		}
	}

	return *(new Type::List::Line());
}

// get the list of procs that modifies the given variable
const Type::List::Proc& Modifies::getProcsModify(Type::Index::Var var){
	if(Utilities::isValidVarIndex(var) && isVarExistsInProc(var)){
		return modifiesProcTable.at(var);
	}

	return *(new Type::List::Proc());
}

// get the last line where the given variable is modified before the given line
Type::Index::Line Modifies::getLastModified(Type::Index::Line line, Type::Index::Var var){
	// if the given line and var indices are valid, last modified matrix is not empty
	if(Utilities::isValidLineIndex(line) && Utilities::isValidVarIndex(var) 
		&& !lastModifiedLVMatrix.empty()){
			// check if the line exists in the matrix
			bool isLineExists = (unsigned int)line < lastModifiedLVMatrix.size();
			if(isLineExists){
				// check if the var exists in the row
				Type::List::Line row = lastModifiedLVMatrix.at(line);
				bool isVarExists = (unsigned int) var < row.size();
				if(isVarExists){
					return row.at(var);
				}
			}
	}
	return -1;
}

// get the next line where the given variable is modified after the given line
Type::Index::Line Modifies::getNextModified(Type::Index::Line line, Type::Index::Var var){
	// if the given line and var indices are valid, next modified matrix is not empty
	if(Utilities::isValidLineIndex(line) && Utilities::isValidVarIndex(var) 
		&& !nextModifiedLVMatrix.empty()){
			// check if the line exists in the matrix
			bool isLineExists = (unsigned int) line < nextModifiedLVMatrix.size();
			if(isLineExists){
				// check if the var exists in the row
				Type::List::Line row = nextModifiedLVMatrix.at(line);
				bool isVarExists = (unsigned int) var < row.size();
				if(isVarExists){
					return row.at(var);
				}
			}
	}
	return -1;
}

// check if the given line modifies the given variable 
bool Modifies::isLineModifies(Type::Index::Line line, Type::Index::Var var){
	if(!Utilities::isValidLineIndex(line) || !Utilities::isValidVarIndex(var)){
		return false;
	}

	if(!modifiesLineBitMap.empty() && (unsigned) line < modifiesLineBitMap.size()){
		Type::List::Bit varBitList = modifiesLineBitMap.at(line);
		if((unsigned) var < varBitList.size()){
			return varBitList.at(var);
		}
	}else if(isLineExistsInVar(line)){
		Type::List::Var varList = modifiesLVarTable.at(line);
		return Utilities::isExistInList(varList, var);
	}

	return false;
}

// check if the given proc modifies the given variable 
bool Modifies::isProcModifies(Type::Index::Proc proc, Type::Index::Var var){
	if(!Utilities::isValidProcIndex(proc) || !Utilities::isValidVarIndex(var)){
		return false;
	}

	if(!modifiesProcBitMap.empty() && (unsigned) proc < modifiesProcBitMap.size()){
		Type::List::Bit varBitList = modifiesProcBitMap.at(proc);
		if((unsigned) var < varBitList.size()){
			return varBitList.at(var);
		}
	}else if(isProcExistsInVar(proc)){
		Type::List::Var varList = modifiesPVarTable.at(proc);
		return Utilities::isExistInList(varList, var);
	}

	return false;
}

// check if the given variable exists in line
bool Modifies::isVarExistsInLine(Type::Index::Var var){
	return modifiesLineTable.find(var) != modifiesLineTable.end();
}

// check if the given line exists in var
bool Modifies::isLineExistsInVar(Type::Index::Line line){
	return modifiesLVarTable.find(line) != modifiesLVarTable.end();
}

// check if the given variable exists in proc
bool Modifies::isVarExistsInProc(Type::Index::Var var){
	return modifiesProcTable.find(var) != modifiesProcTable.end();
}

// check if the given proc exists in var
bool Modifies::isProcExistsInVar(Type::Index::Proc proc){
	return modifiesPVarTable.find(proc) != modifiesPVarTable.end();
}